{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8cf2c066-3895-4744-b677-caf60cb542c9",
   "metadata": {},
   "source": [
    "# Nonlinear regression: Estimating reaction parameters\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e46ce635-ec5c-4bf5-b72d-8073962d5cce",
   "metadata": {},
   "source": [
    "## Motivation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "00c91d8a-6df0-4c60-b3cd-9f13bcf5bb96",
   "metadata": {},
   "source": [
    "### What and why\n",
    "Ready to dive into nonlinear regression? In nonlinear regression models, the relationship between the regression parameters and the outcome variable is nonlinear.  Here are a couple of regression models where the regression parameters $a$, $b$, and $c$ have a nonlinear relationship with the dependent variable $y$:\n",
    "\n",
    "$$y(x) = a\\cdot x^b+c$$\n",
    "$$y(x) = \\dfrac{a\\cdot x}{b+x}$$\n",
    "$$y(x) = \\dfrac{a}{1+b^{x-c}}$$\n",
    "\n",
    "Nonlinear regression is essential in fields with complex relationships, providing flexibility for more accurate modeling. Notable applications include:\n",
    "\n",
    "- **Biology and Medicine**: Understanding growth rates and enzyme kinetics.\n",
    "- **Economics**: Analyzing complex demand and price curves.\n",
    "- **Environmental Science**: Uncovering relationships between crop yields and soil conditions.\n",
    "- **Physics and Engineering**: Understanding radioactive decay or modeling properties of materials.\n",
    "\n",
    "These applications benefit from nonlinear regression's ability to deliver more accurate models and retain known model form for predictions and insights, especially in situations where linear regression approaches may fall short."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c40cce04-4ad1-4740-912a-82f0902c2139",
   "metadata": {},
   "source": [
    "### The nonlinear regression problem\n",
    "The goal of nonlinear regression is to find the parameters that minimize the model error, typically measured by the squared error between the model’s predictions,  $y(x_i; \\text{regression parameters})$ and the actual data points $y_i$. In optimization terms, this process involves:\n",
    "- **Decision variables**: The regression parameters (e.g., $a$ and $b$ in our example below) that define the model.\n",
    "- **Data**: The \"variables\" from the regression model (e.g., y(x) and x) become data points $y_i$ and $x_i$ for all data points $i$.\n",
    "- **Objective**: Commonly, we minimize the sum of the squared error between the model predictions $y_{\\rm model}(x_i; a, b)$ and observed values $y_i$.  Sometimes, absolute error is used as an alternative.\n",
    "- **Constraints**: Often, nonlinear regression has no natural constraints, though bounds can be set on parameters as needed. In this exercise we will, we define $y_{\\rm model}(x)$ to establish our objective.\n",
    "\n",
    "Here’s the general form for least squares regression:\n",
    "\\begin{align*}\n",
    "{\\rm minimize}_{a,b} & \\quad \\displaystyle \\sum_{\\text{data points }i}   \\left(y_\\text{model}(x_i; a,b) - y_i\\right)^2\n",
    "\\end{align*}\n",
    "\n",
    "And, if we use our second example model from above $y(x) = \\dfrac{a\\cdot x}{b+x}$, this would be \n",
    "\\begin{align*}\n",
    "{\\rm minimize}_{a,b} & \\quad \\displaystyle \\sum_{\\text{data points }i}   \\left(\\dfrac{a\\cdot x_i}{b+x_i} - y_i\\right)^2\n",
    "\\end{align*}\n",
    "\n",
    "\n",
    "\n",
    "In many application such problems are solved using a reduced gradient method that cannot guarantee global optimality. In nonlinear regression, it is possible to have multiple local optima because they are typically nonconvex. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79390d07-ae54-4832-b2e1-b81622c0ad8c",
   "metadata": {},
   "source": [
    "### Comparison to linear regression\n",
    "Linear regression is often the first and most widely used type of regression, where the relationship between parameters and the predicted value $y(x)$ is linear. For example:\n",
    "$$y(x) = a\\cdot x + b$$\n",
    "$$y(x) = a\\cdot x + b\\cdot z + c$$\n",
    "\n",
    "Even with terms like $x^2$ or $x\\cdot e^x$, a regression model can be trained using linear regression as long as the parameters ($a$, $b$, and $c$) are linear with respect to $y(x)$:\n",
    "$$y(x) = a\\cdot xz + b\\cdot x^2 + c\\cdot z$$\n",
    "$$y(x) = a\\cdot xe^x + b\\frac{1}{x} + c$$\n",
    "\n",
    "This is because, in the optimization model, the regression coefficients $a$, $b$, and $c$ are the actual variables, while the independent and dependent variables, $x$ and $y$ are simply data points rather than variables themselves. So, the use of the word 'variable' in this notebook can get a little tricky! To keep things clear, we’ll refer to the optimization variables as 'decision variables' wherever possible.\n",
    "\n",
    "Linear regression offers a closed-form solution, allowing parameters to be calculated directly—an advantage that makes it fast in tools like Excel, R, and Python.\n",
    "\n",
    "Nonlinear regression, however, lacks this simplicity. Instead, we solve an optimization problem to find the parameters. Though more complex, this approach is essential for modeling curved or intricate data relationships, providing a flexible and accurate fit.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad9c53cd-0f12-4099-8f03-542dd3cd7961",
   "metadata": {},
   "source": [
    "In this notebook, we will learn how to write a nonlinear regression model where the goal is to determine the regression parameters that minimize the least squares error.\n",
    "\n",
    "Here are a few handy resources to have ready:\n",
    "- [Gurobi Python Documentation](https://docs.gurobi.com/projects/optimizer/en/current/reference/python.html)\n",
    "- [Gurobi Python Examples](https://docs.gurobi.com/projects/examples/en/current/examples/python.html)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25ba7fe7-068d-4e9b-a25e-7e77f83d9364",
   "metadata": {},
   "source": [
    " ## Problem Description "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6ad2892-6047-4bb5-bfea-0c66e01cc80e",
   "metadata": {},
   "source": [
    "### Michaelis-Menten Enzyme Kinetics\n",
    "[Michaelis-Menten](https://en.wikipedia.org/wiki/Michaelis%E2%80%93Menten_kinetics) kinetics describes how quickly a reaction happens when an enzyme—a type of molecule that speeds up processes in the body or other reactions—interacts with other molecules. Initially, as the amount of molecules $x$ increases, the reaction speed, $y$, increases. But at a certain point, adding more molecules doesn’t increase the speed much because the enzyme is already working as fast as it can.\n",
    "\n",
    "<img src=\"https://raw.githubusercontent.com/Gurobi/modeling-examples/master/optimization202/Modeling_Session_1/mm.png\" alt=\"Image\" width=\"600\"/>\n",
    "\n",
    "Two key things come from this: the maximum speed the enzyme can reach $V_{max}$ and the concentration level at which it’s working at half that maximum speed $K_m$. These insights help us understand efficiency and limitations, which is valuable for making decisions in fields like drug development, where knowing an enzyme's limits can guide dosage or predict interactions.\n",
    "\n",
    "In Michaelis-Menten kinetics, we can model the speed of an enzymatic reaction can be with the following equations:\n",
    "\n",
    "$$\n",
    "[\\text{Speed of reaction}] = \\frac{{\\bf V_{max}}\\cdot [\\text{Initial concentration}]}{K_m + [\\text{Initial concentration}]}\n",
    "$$\n",
    "or\n",
    "$$\n",
    "y(x) = \\frac{{\\bf V_{max}}\\cdot x}{K_m + x}\n",
    "$$\n",
    "\n",
    "where the regression parameters that we will find in this notebook are $V_{max}$ and $K_m$.  We will be given data points $i$ for \n",
    "- The independent variable $x$ which is the concentration. Each data point will be $x_i$.\n",
    "- The dependent variable $y(x)$ which is the speed of the reaction that we are trying to model.  Each data point will be $y_i$."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84e9218c-3b7c-404d-9996-6497da097eb0",
   "metadata": {},
   "source": [
    "## Solution approach\n",
    "\n",
    "Your goal in this notebook is to write a model to find the optimal regression parameters $V_{max}$ and $K_m$. To achieve this, you will formulate a nonlinear least squares regression problem.\n",
    "\n",
    "\n",
    "### Let's get started\n",
    "Throughout the rest of this notebook, you will\n",
    "1. Define the data\n",
    "1. Create the Gurobi `model` object\n",
    "1. Add decision variables\n",
    "1. Add constraints\n",
    "1. Set the objective function\n",
    "1. Solve the model\n",
    "1. Retrieve solution values"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2e63af6-a5a9-4476-8495-9da2604582de",
   "metadata": {},
   "source": [
    "## 0. The obligatory part\n",
    "This is the obligatory part of all python code: installing and importing packages.  \n",
    "\n",
    "First, let's install a few packages as needed, then import them"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bf53b323-9e7b-40c7-a8d8-28723d77ae3a",
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install gurobipy\n",
    "!pip install plotly\n",
    "!pip install numpy\n",
    "!pip install pandas\n",
    "!pip install openpyxl"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "b2a60227-b5f8-44cb-9b0b-08fbcc7e873c",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gurobipy as gp\n",
    "from gurobipy import GRB\n",
    "\n",
    "import pandas as pd\n",
    "import plotly.express as px\n",
    "import plotly.graph_objects as go\n",
    "import plotly.io as pio\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "34b8555e-0ff4-4a1d-828a-6c0ea1cfdd31",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success alert-dismissible\">\n",
    "  <a href=\"#\" class=\"close\" data-dismiss=\"alert\" aria-label=\"close\">&times;</a>\n",
    "    <strong>Quick Tip</strong>\n",
    "    <p>The first two import lines above will be needed each time you want to use gurobipy. You are encouraged to completely forget this part and copy-and-paste it for each new Gurobi model you write  ;).</p>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c7ea527-2356-469f-ab43-b668060de85f",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 1.  Load and visualize data\n",
    "\n",
    "In this section, we will read in the benchtop experimental data that we will use to train our model.  We will be given 10 data values for $x$ and $y$.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2d903089-2524-487b-8639-2b9dfd395525",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Here are the x values:\n",
      "{0.0: 0.05, 1.0: 0.1, 2.0: 0.25, 3.0: 0.5, 4.0: 1.0, 5.0: 2.5, 6.0: 5.0, 7.0: 8.0, 8.0: 20.0, 9.0: 30.0}\n",
      "\n",
      "Here are the y values:\n",
      "{0.0: 2.98, 1.0: 5.2, 2.0: 14.38, 3.0: 30.3, 4.0: 48.99, 5.0: 86.25, 6.0: 112.57, 7.0: 136.24, 8.0: 169.97, 9.0: 177.71}\n"
     ]
    }
   ],
   "source": [
    "# Data\n",
    "path = 'https://raw.githubusercontent.com/Gurobi/modeling-examples/master/optimization202/Modeling_Session_1/'\n",
    "df = pd.read_excel(path + 'MMdata.xlsx') \n",
    "\n",
    "# Use the gurobipy multidict structure to unpack the data in our data frame\n",
    "# Data is from https://cran.r-project.org/web/packages/renz/vignettes/dirMM.html\n",
    "points, xtrain, ytrain = gp.multidict({\n",
    "        row['index']: [float(row['x']), float(row['y'])] for ind,row in df.iterrows()\n",
    "    }) \n",
    "\n",
    "# Show the data:\n",
    "print(\"Here are the x values:\")\n",
    "print(xtrain)\n",
    "\n",
    "print(\"\\nHere are the y values:\")\n",
    "print(ytrain)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f2536dc-8795-4848-8ac7-22ad9cdb49c7",
   "metadata": {},
   "source": [
    "### View the data points"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "0693fe8b-08ac-4bd2-af7f-dcbd531888e3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.plotly.v1+json": {
       "config": {
        "plotlyServerURL": "https://plot.ly"
       },
       "data": [
        {
         "mode": "markers",
         "name": "Training data",
         "type": "scatter",
         "x": [
          0.05,
          0.1,
          0.25,
          0.5,
          1,
          2.5,
          5,
          8,
          20,
          30
         ],
         "y": [
          2.98,
          5.2,
          14.38,
          30.3,
          48.99,
          86.25,
          112.57,
          136.24,
          169.97,
          177.71
         ]
        }
       ],
       "layout": {
        "template": {
         "data": {
          "bar": [
           {
            "error_x": {
             "color": "rgb(36,36,36)"
            },
            "error_y": {
             "color": "rgb(36,36,36)"
            },
            "marker": {
             "line": {
              "color": "white",
              "width": 0.5
             },
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "bar"
           }
          ],
          "barpolar": [
           {
            "marker": {
             "line": {
              "color": "white",
              "width": 0.5
             },
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "barpolar"
           }
          ],
          "carpet": [
           {
            "aaxis": {
             "endlinecolor": "rgb(36,36,36)",
             "gridcolor": "white",
             "linecolor": "white",
             "minorgridcolor": "white",
             "startlinecolor": "rgb(36,36,36)"
            },
            "baxis": {
             "endlinecolor": "rgb(36,36,36)",
             "gridcolor": "white",
             "linecolor": "white",
             "minorgridcolor": "white",
             "startlinecolor": "rgb(36,36,36)"
            },
            "type": "carpet"
           }
          ],
          "choropleth": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "type": "choropleth"
           }
          ],
          "contour": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "colorscale": [
             [
              0,
              "#440154"
             ],
             [
              0.1111111111111111,
              "#482878"
             ],
             [
              0.2222222222222222,
              "#3e4989"
             ],
             [
              0.3333333333333333,
              "#31688e"
             ],
             [
              0.4444444444444444,
              "#26828e"
             ],
             [
              0.5555555555555556,
              "#1f9e89"
             ],
             [
              0.6666666666666666,
              "#35b779"
             ],
             [
              0.7777777777777778,
              "#6ece58"
             ],
             [
              0.8888888888888888,
              "#b5de2b"
             ],
             [
              1,
              "#fde725"
             ]
            ],
            "type": "contour"
           }
          ],
          "contourcarpet": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "type": "contourcarpet"
           }
          ],
          "heatmap": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "colorscale": [
             [
              0,
              "#440154"
             ],
             [
              0.1111111111111111,
              "#482878"
             ],
             [
              0.2222222222222222,
              "#3e4989"
             ],
             [
              0.3333333333333333,
              "#31688e"
             ],
             [
              0.4444444444444444,
              "#26828e"
             ],
             [
              0.5555555555555556,
              "#1f9e89"
             ],
             [
              0.6666666666666666,
              "#35b779"
             ],
             [
              0.7777777777777778,
              "#6ece58"
             ],
             [
              0.8888888888888888,
              "#b5de2b"
             ],
             [
              1,
              "#fde725"
             ]
            ],
            "type": "heatmap"
           }
          ],
          "heatmapgl": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "colorscale": [
             [
              0,
              "#440154"
             ],
             [
              0.1111111111111111,
              "#482878"
             ],
             [
              0.2222222222222222,
              "#3e4989"
             ],
             [
              0.3333333333333333,
              "#31688e"
             ],
             [
              0.4444444444444444,
              "#26828e"
             ],
             [
              0.5555555555555556,
              "#1f9e89"
             ],
             [
              0.6666666666666666,
              "#35b779"
             ],
             [
              0.7777777777777778,
              "#6ece58"
             ],
             [
              0.8888888888888888,
              "#b5de2b"
             ],
             [
              1,
              "#fde725"
             ]
            ],
            "type": "heatmapgl"
           }
          ],
          "histogram": [
           {
            "marker": {
             "line": {
              "color": "white",
              "width": 0.6
             }
            },
            "type": "histogram"
           }
          ],
          "histogram2d": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "colorscale": [
             [
              0,
              "#440154"
             ],
             [
              0.1111111111111111,
              "#482878"
             ],
             [
              0.2222222222222222,
              "#3e4989"
             ],
             [
              0.3333333333333333,
              "#31688e"
             ],
             [
              0.4444444444444444,
              "#26828e"
             ],
             [
              0.5555555555555556,
              "#1f9e89"
             ],
             [
              0.6666666666666666,
              "#35b779"
             ],
             [
              0.7777777777777778,
              "#6ece58"
             ],
             [
              0.8888888888888888,
              "#b5de2b"
             ],
             [
              1,
              "#fde725"
             ]
            ],
            "type": "histogram2d"
           }
          ],
          "histogram2dcontour": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "colorscale": [
             [
              0,
              "#440154"
             ],
             [
              0.1111111111111111,
              "#482878"
             ],
             [
              0.2222222222222222,
              "#3e4989"
             ],
             [
              0.3333333333333333,
              "#31688e"
             ],
             [
              0.4444444444444444,
              "#26828e"
             ],
             [
              0.5555555555555556,
              "#1f9e89"
             ],
             [
              0.6666666666666666,
              "#35b779"
             ],
             [
              0.7777777777777778,
              "#6ece58"
             ],
             [
              0.8888888888888888,
              "#b5de2b"
             ],
             [
              1,
              "#fde725"
             ]
            ],
            "type": "histogram2dcontour"
           }
          ],
          "mesh3d": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "type": "mesh3d"
           }
          ],
          "parcoords": [
           {
            "line": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "parcoords"
           }
          ],
          "pie": [
           {
            "automargin": true,
            "type": "pie"
           }
          ],
          "scatter": [
           {
            "fillpattern": {
             "fillmode": "overlay",
             "size": 10,
             "solidity": 0.2
            },
            "type": "scatter"
           }
          ],
          "scatter3d": [
           {
            "line": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scatter3d"
           }
          ],
          "scattercarpet": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scattercarpet"
           }
          ],
          "scattergeo": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scattergeo"
           }
          ],
          "scattergl": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scattergl"
           }
          ],
          "scattermapbox": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scattermapbox"
           }
          ],
          "scatterpolar": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scatterpolar"
           }
          ],
          "scatterpolargl": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scatterpolargl"
           }
          ],
          "scatterternary": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scatterternary"
           }
          ],
          "surface": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "colorscale": [
             [
              0,
              "#440154"
             ],
             [
              0.1111111111111111,
              "#482878"
             ],
             [
              0.2222222222222222,
              "#3e4989"
             ],
             [
              0.3333333333333333,
              "#31688e"
             ],
             [
              0.4444444444444444,
              "#26828e"
             ],
             [
              0.5555555555555556,
              "#1f9e89"
             ],
             [
              0.6666666666666666,
              "#35b779"
             ],
             [
              0.7777777777777778,
              "#6ece58"
             ],
             [
              0.8888888888888888,
              "#b5de2b"
             ],
             [
              1,
              "#fde725"
             ]
            ],
            "type": "surface"
           }
          ],
          "table": [
           {
            "cells": {
             "fill": {
              "color": "rgb(237,237,237)"
             },
             "line": {
              "color": "white"
             }
            },
            "header": {
             "fill": {
              "color": "rgb(217,217,217)"
             },
             "line": {
              "color": "white"
             }
            },
            "type": "table"
           }
          ]
         },
         "layout": {
          "annotationdefaults": {
           "arrowhead": 0,
           "arrowwidth": 1
          },
          "autotypenumbers": "strict",
          "coloraxis": {
           "colorbar": {
            "outlinewidth": 1,
            "tickcolor": "rgb(36,36,36)",
            "ticks": "outside"
           }
          },
          "colorscale": {
           "diverging": [
            [
             0,
             "rgb(103,0,31)"
            ],
            [
             0.1,
             "rgb(178,24,43)"
            ],
            [
             0.2,
             "rgb(214,96,77)"
            ],
            [
             0.3,
             "rgb(244,165,130)"
            ],
            [
             0.4,
             "rgb(253,219,199)"
            ],
            [
             0.5,
             "rgb(247,247,247)"
            ],
            [
             0.6,
             "rgb(209,229,240)"
            ],
            [
             0.7,
             "rgb(146,197,222)"
            ],
            [
             0.8,
             "rgb(67,147,195)"
            ],
            [
             0.9,
             "rgb(33,102,172)"
            ],
            [
             1,
             "rgb(5,48,97)"
            ]
           ],
           "sequential": [
            [
             0,
             "#440154"
            ],
            [
             0.1111111111111111,
             "#482878"
            ],
            [
             0.2222222222222222,
             "#3e4989"
            ],
            [
             0.3333333333333333,
             "#31688e"
            ],
            [
             0.4444444444444444,
             "#26828e"
            ],
            [
             0.5555555555555556,
             "#1f9e89"
            ],
            [
             0.6666666666666666,
             "#35b779"
            ],
            [
             0.7777777777777778,
             "#6ece58"
            ],
            [
             0.8888888888888888,
             "#b5de2b"
            ],
            [
             1,
             "#fde725"
            ]
           ],
           "sequentialminus": [
            [
             0,
             "#440154"
            ],
            [
             0.1111111111111111,
             "#482878"
            ],
            [
             0.2222222222222222,
             "#3e4989"
            ],
            [
             0.3333333333333333,
             "#31688e"
            ],
            [
             0.4444444444444444,
             "#26828e"
            ],
            [
             0.5555555555555556,
             "#1f9e89"
            ],
            [
             0.6666666666666666,
             "#35b779"
            ],
            [
             0.7777777777777778,
             "#6ece58"
            ],
            [
             0.8888888888888888,
             "#b5de2b"
            ],
            [
             1,
             "#fde725"
            ]
           ]
          },
          "colorway": [
           "#1F77B4",
           "#FF7F0E",
           "#2CA02C",
           "#D62728",
           "#9467BD",
           "#8C564B",
           "#E377C2",
           "#7F7F7F",
           "#BCBD22",
           "#17BECF"
          ],
          "font": {
           "color": "rgb(36,36,36)"
          },
          "geo": {
           "bgcolor": "white",
           "lakecolor": "white",
           "landcolor": "white",
           "showlakes": true,
           "showland": true,
           "subunitcolor": "white"
          },
          "hoverlabel": {
           "align": "left"
          },
          "hovermode": "closest",
          "mapbox": {
           "style": "light"
          },
          "paper_bgcolor": "white",
          "plot_bgcolor": "white",
          "polar": {
           "angularaxis": {
            "gridcolor": "rgb(232,232,232)",
            "linecolor": "rgb(36,36,36)",
            "showgrid": false,
            "showline": true,
            "ticks": "outside"
           },
           "bgcolor": "white",
           "radialaxis": {
            "gridcolor": "rgb(232,232,232)",
            "linecolor": "rgb(36,36,36)",
            "showgrid": false,
            "showline": true,
            "ticks": "outside"
           }
          },
          "scene": {
           "xaxis": {
            "backgroundcolor": "white",
            "gridcolor": "rgb(232,232,232)",
            "gridwidth": 2,
            "linecolor": "rgb(36,36,36)",
            "showbackground": true,
            "showgrid": false,
            "showline": true,
            "ticks": "outside",
            "zeroline": false,
            "zerolinecolor": "rgb(36,36,36)"
           },
           "yaxis": {
            "backgroundcolor": "white",
            "gridcolor": "rgb(232,232,232)",
            "gridwidth": 2,
            "linecolor": "rgb(36,36,36)",
            "showbackground": true,
            "showgrid": false,
            "showline": true,
            "ticks": "outside",
            "zeroline": false,
            "zerolinecolor": "rgb(36,36,36)"
           },
           "zaxis": {
            "backgroundcolor": "white",
            "gridcolor": "rgb(232,232,232)",
            "gridwidth": 2,
            "linecolor": "rgb(36,36,36)",
            "showbackground": true,
            "showgrid": false,
            "showline": true,
            "ticks": "outside",
            "zeroline": false,
            "zerolinecolor": "rgb(36,36,36)"
           }
          },
          "shapedefaults": {
           "fillcolor": "black",
           "line": {
            "width": 0
           },
           "opacity": 0.3
          },
          "ternary": {
           "aaxis": {
            "gridcolor": "rgb(232,232,232)",
            "linecolor": "rgb(36,36,36)",
            "showgrid": false,
            "showline": true,
            "ticks": "outside"
           },
           "baxis": {
            "gridcolor": "rgb(232,232,232)",
            "linecolor": "rgb(36,36,36)",
            "showgrid": false,
            "showline": true,
            "ticks": "outside"
           },
           "bgcolor": "white",
           "caxis": {
            "gridcolor": "rgb(232,232,232)",
            "linecolor": "rgb(36,36,36)",
            "showgrid": false,
            "showline": true,
            "ticks": "outside"
           }
          },
          "title": {
           "x": 0.05
          },
          "xaxis": {
           "automargin": true,
           "gridcolor": "rgb(232,232,232)",
           "linecolor": "rgb(36,36,36)",
           "showgrid": false,
           "showline": true,
           "ticks": "outside",
           "title": {
            "standoff": 15
           },
           "zeroline": false,
           "zerolinecolor": "rgb(36,36,36)"
          },
          "yaxis": {
           "automargin": true,
           "gridcolor": "rgb(232,232,232)",
           "linecolor": "rgb(36,36,36)",
           "showgrid": false,
           "showline": true,
           "ticks": "outside",
           "title": {
            "standoff": 15
           },
           "zeroline": false,
           "zerolinecolor": "rgb(36,36,36)"
          }
         }
        },
        "title": {
         "text": "Nonlinear regression example: Reaction Speed vs Concentration"
        },
        "xaxis": {
         "title": {
          "text": "x"
         }
        },
        "yaxis": {
         "title": {
          "text": "y"
         }
        }
       }
      }
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Create a figure\n",
    "fig = go.Figure()\n",
    "xtrain_arr = np.array(list(xtrain.values()))\n",
    "ytrain_arr = np.array(list(ytrain.values()))\n",
    "\n",
    "# Add the first line\n",
    "fig.add_trace(go.Scatter(x=xtrain_arr, y=ytrain_arr, mode='markers', name='Training data'))\n",
    "\n",
    "# Update layout \n",
    "fig.update_layout(title='Nonlinear regression example: Reaction Speed vs Concentration',\n",
    "                  xaxis_title='x',\n",
    "                  yaxis_title='y',\n",
    "                  template='simple_white')\n",
    "# Show the figure\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3648863e-a92d-4325-b038-cf2ee943e45c",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 2. Begin building the model\n",
    "In the next four steps, we will be creating the following model:\n",
    "$$\n",
    "\\begin{align*}\n",
    "{\\rm minimize} &  \\quad \\displaystyle \\sum_{\\text{data points }i}   \\left(y_{\\text{model},i} - y_i\\right)^2 \\\\ \\\\\n",
    "{\\rm s.t.} & \\quad y_{\\text{model},i} =  \\frac{{\\bf V_{max}}\\cdot x_i}{K_m + x_i} & \\forall \\text{ data points }i  \\\\ \n",
    "\\end{align*}\n",
    "$$\n",
    "\n",
    "\n",
    "where we have:\n",
    "- **Decision variables**: The main decision variables are regression parameters $V_\\text{max}$ and $K_m$. We are also creating and defining variables for each prediction value $y_{\\text{model},i}$\n",
    "- **Data**: Observation data points $x_i$ and $y_i$\n",
    "- **Constraints**: We have a constraints for every data point $i$ to definte the prediction value $y_{\\text{model},i}$\n",
    "- **Objective**: We are minimizing the sum of the squared error\n",
    "\n",
    "We will also add bounds to all of our variables as it is a best practice --- and makes the model run MUCH faster.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ecebe3e-924b-4005-9748-03a96047b686",
   "metadata": {},
   "source": [
    "To start, we will need to create the [model()](https://docs.gurobi.com/projects/optimizer/en/current/reference/python/model.html) object in Gurobi.  The Model object holds a single optimization problem. It consists of a set of variables, a set of constraints, and an objective function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "a4d15290-83c7-46c9-bd7d-de202a18a2cf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Restricted license - for non-production use only - expires 2026-11-23\n"
     ]
    }
   ],
   "source": [
    "# Declare and initialize model\n",
    "model = gp.Model(\"Nonlinear regression\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "323708f5-8eab-4496-9cdd-4a07d6e48c79",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 3. Add Decision variables\n",
    "The main decision variables are regression parameters $V_\\text{max}$ and $K_m$ (in your model, name these `Vmax` and `Km`). We are also creating and defining each prediction value $y_{\\text{model},i}$ for every data point $i$.\n",
    "\n",
    "**Bounding the decision variables**: Providing bounds on the variables is important for model performance. In our case, you can assume that $V_\\text{max}$ and $K_m$ will each be between zero and 1000. For each $y_{\\text{model},i}$, you can use a lower bound of $-2 * y_\\max$ and an upper bound of $2*y_\\max$ where $y_\\max$ is the largest reaction time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "7b93ec63-f6a6-49dc-a4a6-a7be0cb21c5e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create decision variables \n",
    "Vmax = model.addVar(name='Vmax', ub=1000)\n",
    "Km = model.addVar(name='Km', ub=1000)\n",
    "\n",
    "# Create additional variables to define the model\n",
    "ymax = max(ytrain.values()) # Used in a thoughtful upper bound on ymodel\n",
    "ymodel = model.addVars(points, lb=-2*ymax, ub=2*ymax, name=\"ymodel\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "151df4e0-179f-496a-917b-457658e4bc71",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success alert-dismissible\">\n",
    "  <a href=\"#\" class=\"close\" data-dismiss=\"alert\" aria-label=\"close\">&times;</a>\n",
    "    <strong>Quick Tip</strong>\n",
    "    <p>Remember to bound your variables where possible.  This helps encourage a speedy solve.</p>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a842859c-d269-4a3a-b1f2-b3688748dd48",
   "metadata": {},
   "source": [
    "Next, let's double check the variables look like we would expect."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "bde6c419-23d4-4f59-9dee-1e0712f1c96f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The model variables are: \n",
      " - Vmax with lower bound of 0.0 and upper bound of 1000.0\n",
      " - Km with lower bound of 0.0 and upper bound of 1000.0\n",
      " - ymodel[0.0] with lower bound of -355.42 and upper bound of 355.42\n",
      " - ymodel[1.0] with lower bound of -355.42 and upper bound of 355.42\n",
      " - ymodel[2.0] with lower bound of -355.42 and upper bound of 355.42\n",
      " - ymodel[3.0] with lower bound of -355.42 and upper bound of 355.42\n",
      " - ymodel[4.0] with lower bound of -355.42 and upper bound of 355.42\n",
      " - ymodel[5.0] with lower bound of -355.42 and upper bound of 355.42\n",
      " - ymodel[6.0] with lower bound of -355.42 and upper bound of 355.42\n",
      " - ymodel[7.0] with lower bound of -355.42 and upper bound of 355.42\n",
      " - ymodel[8.0] with lower bound of -355.42 and upper bound of 355.42\n",
      " - ymodel[9.0] with lower bound of -355.42 and upper bound of 355.42\n"
     ]
    }
   ],
   "source": [
    "# Check your work by printing out the variables\n",
    "model.update()\n",
    "print(\"The model variables are: \")\n",
    "for v in model.getVars():\n",
    "    print(f\" - {v.varname} with lower bound of {v.lb} and upper bound of {v.ub}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51a1f7a9-0c8f-4d37-8434-a652a314a5d7",
   "metadata": {},
   "source": [
    "---\n",
    "## 4. Add Constraints\n",
    "We will begin adding the constraints that define our problem. For this model, all we need to do is define $y_{\\text{model},i}$, where\n",
    "$$y_{\\text{model},i} = \\frac{{\\bf V_{max}}\\cdot x_i}{K_m + x_i}  \\quad  \\quad \\forall \\text{ data points }i$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "38341cae-b427-4855-a4a8-064ba50e3cd5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Write out your constraint to define ymodel[i] for each data poitn in points\n",
    "constrs = model.addConstrs((ymodel[i]*(Km + xtrain[i])   == Vmax*xtrain[i] for i in points), name=\"Define_ymodel\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22ae2755-1409-4550-9a5e-ce5ef085b98f",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success alert-dismissible\">\n",
    "  <a href=\"#\" class=\"close\" data-dismiss=\"alert\" aria-label=\"close\">&times;</a>\n",
    "    <strong>Quick Tip</strong>\n",
    "    <p>Remember to think about reformulating your equations.  This constraint could easily be written in two ways. </p>\n",
    "    <p>Option 1:</p>\n",
    "    <pre>ymodel[i] = Vmax*x[i] / (Km + x[i])</pre>\n",
    "    <p></p>\n",
    "    <p>Option 2:</p>\n",
    "    <pre>ymodel[i](Km + x[i]) = Vmax*x[i] </pre>\n",
    "    <p></p>\n",
    "    <p>The second option would be easier for the solver to use because it is a quadratic equation rather than a fully nonlinear one.</p>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d799707-45bc-42d7-8ef1-11be91aab5ae",
   "metadata": {},
   "source": [
    "\n",
    "---\n",
    "## 5. Set the objective\n",
    "The goal is to minimize the sum of the squared error. In this step, we will write the following objective:\n",
    "$$\n",
    "\\begin{align*}\n",
    "{\\rm minimize} &  \\quad \\displaystyle \\sum_{\\text{data points }i}   \\left(y_{\\text{model},i} - y_i\\right)^2 \n",
    "\\end{align*}\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "2fb54b23-d80e-4a37-8ccf-d5c78d31c50e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Write the objective\n",
    "model.setObjective(sum((ymodel[i] - ytrain[i])**2 for i in points), sense=GRB.MINIMIZE)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "853d8678-b067-4ba6-940a-98ef7b469f18",
   "metadata": {},
   "source": [
    "### Celebrate and check your work"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "7ed492af-3d18-44ba-afb8-7e8b53efaec0",
   "metadata": {},
   "outputs": [],
   "source": [
    "model.write('nonlinear_regression.lp')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "97582f66-df7d-4581-acc5-0cbab3b20e73",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success alert-dismissible\">\n",
    "  <a href=\"#\" class=\"close\" data-dismiss=\"alert\" aria-label=\"close\">&times;</a>\n",
    "    <strong>Quick Tip</strong> \n",
    "    <p>In the cell above, we wrote out the model as an LP file.  This is a human-readable format that can allow you to check to make sure your constraints and objectives look right.  This has been saved to this local directory.  \n",
    "    <p>Take a look at nonlinear_regression.lp. Does everything look correct?  If so, please consider celebrating. Think about all the enzymatic reactions happening in your body. Go eat a donut -- make more reactions.</p>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5fbb3368-7c37-40d7-8de0-6d493db7768e",
   "metadata": {},
   "source": [
    "<a id='6_optimize'></a>\n",
    "---\n",
    "## 6. Solve the model \n",
    "We use the [optimize()](https://www.gurobi.com/documentation/current/refman/py_model_optimize.html) method of the Gurobi/Python API to solve the problem we have defined for the model object `model`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "e4b41f36-17ff-4748-95c5-d8d101e80231",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Gurobi Optimizer version 12.0.0 build v12.0.0rc1 (mac64[rosetta2] - Darwin 24.1.0 24B83)\n",
      "\n",
      "CPU model: Apple M1\n",
      "Thread count: 8 physical cores, 8 logical processors, using up to 8 threads\n",
      "\n",
      "Optimize a model with 0 rows, 12 columns and 0 nonzeros\n",
      "Model fingerprint: 0xa9dc9e66\n",
      "Model has 10 quadratic objective terms\n",
      "Model has 10 quadratic constraints\n",
      "Coefficient statistics:\n",
      "  Matrix range     [0e+00, 0e+00]\n",
      "  QMatrix range    [1e+00, 1e+00]\n",
      "  QLMatrix range   [5e-02, 3e+01]\n",
      "  Objective range  [6e+00, 4e+02]\n",
      "  QObjective range [2e+00, 2e+00]\n",
      "  Bounds range     [4e+02, 1e+03]\n",
      "  RHS range        [0e+00, 0e+00]\n",
      "\n",
      "Continuous model is non-convex -- solving as a MIP\n",
      "\n",
      "Presolve time: 0.00s\n",
      "Presolved: 40 rows, 13 columns, 100 nonzeros\n",
      "Presolved model has 10 quadratic objective terms\n",
      "Presolved model has 10 bilinear constraint(s)\n",
      "Variable types: 13 continuous, 0 integer (0 binary)\n",
      "Found heuristic solution: objective 76.2644466\n",
      "\n",
      "Root relaxation: objective 0.000000e+00, 5 iterations, 0.01 seconds (0.00 work units)\n",
      "\n",
      "    Nodes    |    Current Node    |     Objective Bounds      |     Work\n",
      " Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time\n",
      "\n",
      "     0     0    0.00000    0   10   76.26445    0.00000   100%     -    0s\n",
      "     0     0    0.00000    0   10   76.26445    0.00000   100%     -    0s\n",
      "     0     2    0.00000    0   10   76.26445    0.00000   100%     -    0s\n",
      "*  775   156              29      76.2644459   75.95079  0.41%   0.7    0s\n",
      "* 1835    51              29      76.2644458   76.22547  0.05%   2.1    0s\n",
      "* 1917    51              26      76.2644456   76.22556  0.05%   2.2    0s\n",
      "* 2134     4              26      76.2644455   76.25478  0.01%   2.2    0s\n",
      "\n",
      "Explored 2299 nodes (4747 simplex iterations) in 0.13 seconds (0.01 work units)\n",
      "Thread count was 8 (of 8 available processors)\n",
      "\n",
      "Solution count 4: 76.2644 76.2644 76.2644 76.2644 \n",
      "\n",
      "Optimal solution found (tolerance 1.00e-04)\n",
      "Best objective 7.626444550834e+01, best bound 7.626444409354e+01, gap 0.0000%\n"
     ]
    }
   ],
   "source": [
    "model.optimize()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "755178b8-997e-4b3c-a153-6e43e1f6b8f0",
   "metadata": {},
   "source": [
    "**Question**: Look at the Gurobi log and reflect on what you see. What type of model was this? Was it convex or non-convex? Is this consistent with what you would expect?  "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "112bf73c-d8d9-4c05-ab54-e548820189a0",
   "metadata": {},
   "source": [
    "### Check your solution"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "31f7a434-c49f-4ab1-a788-3dcae22bbd48",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "An optimial solution was found with objective 76.26444550833548\n",
      "\n",
      "    Variable            X \n",
      "-------------------------\n",
      "        Vmax      195.345 \n",
      "          Km      3.26518 \n",
      " ymodel[0.0]      2.94622 \n",
      " ymodel[1.0]      5.80489 \n",
      " ymodel[2.0]       13.893 \n",
      " ymodel[3.0]       25.941 \n",
      " ymodel[4.0]         45.8 \n",
      " ymodel[5.0]       84.709 \n",
      " ymodel[6.0]      118.174 \n",
      " ymodel[7.0]      138.725 \n",
      " ymodel[8.0]      167.929 \n",
      " ymodel[9.0]      176.171 \n"
     ]
    }
   ],
   "source": [
    "status = model.status\n",
    "if status == GRB.OPTIMAL:\n",
    "    print(f\"An optimal solution was found with objective {model.objVal}\")\n",
    "    model.printAttr('X')\n",
    "else:\n",
    "    print(f\"Model is not optimal, status = {status}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "61cb9017-b863-4b6b-a0e4-65ba30dcf41a",
   "metadata": {},
   "source": [
    "---\n",
    "## 7. View the solution\n",
    "In the next section, we will plot our resulting regression model alongside the data.  \n",
    "\n",
    "We pulled this data from [dirMM - Fitting the Michaelis-Menten Model](https://cran.r-project.org/web/packages/renz/vignettes/dirMM.html).  This R package also gives its values for $V_\\text{max} = 181.182$ and $K_m=3.115$. As you can see our values may differ from this.  Let's plot them and see which looks better.  I have also calculated the Mean Squared Error (MSE) for both lines and added it to the chart."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "2c52078d-528a-4717-bef7-7fc675cd92ab",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.plotly.v1+json": {
       "config": {
        "plotlyServerURL": "https://plot.ly"
       },
       "data": [
        {
         "mode": "markers",
         "name": "Training data",
         "type": "scatter",
         "x": [
          0.05,
          0.1,
          0.25,
          0.5,
          1,
          2.5,
          5,
          8,
          20,
          30
         ],
         "y": [
          2.98,
          5.2,
          14.38,
          30.3,
          48.99,
          86.25,
          112.57,
          136.24,
          169.97,
          177.71
         ]
        },
        {
         "mode": "lines",
         "name": "dir-MM (R-method): Vmax=181.2, Km=3.1 (MSE=47.8)",
         "type": "scatter",
         "x": [
          0.05,
          0.3525252525252525,
          0.6550505050505051,
          0.9575757575757576,
          1.2601010101010102,
          1.5626262626262626,
          1.8651515151515152,
          2.1676767676767676,
          2.47020202020202,
          2.7727272727272725,
          3.075252525252525,
          3.3777777777777778,
          3.68030303030303,
          3.9828282828282826,
          4.2853535353535355,
          4.587878787878788,
          4.89040404040404,
          5.192929292929293,
          5.495454545454545,
          5.797979797979798,
          6.10050505050505,
          6.403030303030303,
          6.705555555555556,
          7.008080808080808,
          7.3106060606060606,
          7.613131313131313,
          7.915656565656565,
          8.218181818181819,
          8.520707070707072,
          8.823232323232324,
          9.125757575757577,
          9.428282828282828,
          9.730808080808082,
          10.033333333333335,
          10.335858585858587,
          10.63838383838384,
          10.940909090909091,
          11.243434343434345,
          11.545959595959596,
          11.84848484848485,
          12.151010101010101,
          12.453535353535354,
          12.756060606060608,
          13.05858585858586,
          13.361111111111112,
          13.663636363636364,
          13.966161616161617,
          14.268686868686869,
          14.571212121212122,
          14.873737373737375,
          15.176262626262627,
          15.47878787878788,
          15.781313131313132,
          16.083838383838383,
          16.386363636363637,
          16.68888888888889,
          16.991414141414143,
          17.293939393939397,
          17.596464646464646,
          17.8989898989899,
          18.201515151515153,
          18.504040404040406,
          18.806565656565656,
          19.10909090909091,
          19.411616161616163,
          19.714141414141416,
          20.01666666666667,
          20.31919191919192,
          20.621717171717172,
          20.924242424242426,
          21.22676767676768,
          21.52929292929293,
          21.831818181818182,
          22.134343434343435,
          22.43686868686869,
          22.739393939393942,
          23.04191919191919,
          23.344444444444445,
          23.6469696969697,
          23.94949494949495,
          24.2520202020202,
          24.554545454545455,
          24.857070707070708,
          25.15959595959596,
          25.462121212121215,
          25.764646464646464,
          26.067171717171718,
          26.36969696969697,
          26.672222222222224,
          26.974747474747478,
          27.277272727272727,
          27.57979797979798,
          27.882323232323234,
          28.184848484848487,
          28.487373737373737,
          28.78989898989899,
          29.092424242424244,
          29.394949494949497,
          29.69747474747475,
          30
         ],
         "y": [
          2.862243285939969,
          18.419628005884324,
          31.480228274411566,
          42.600453885933256,
          52.18281828990961,
          60.525704784218874,
          67.8549940673583,
          74.34482442135051,
          80.13160498069394,
          85.32370879332973,
          90.00832361075982,
          94.25638743903482,
          98.12620571250196,
          101.66614343553654,
          104.91665699836891,
          107.91184720391826,
          110.6806601601191,
          113.24782579621514,
          115.63459853243943,
          117.85934711038831,
          119.9380282462034,
          121.88456995494994,
          123.71118402443854,
          125.42862245992507,
          127.04638928047204,
          128.57291648031935,
          130.01571103490275,
          131.3814783620102,
          132.67622652319793,
          133.90535458187557,
          135.0737278589907,
          136.18574229838498,
          137.24537973713078,
          138.25625554569655,
          139.2216598392202,
          140.14459324977875,
          141.0277980790997,
          141.87378551303743,
          142.68485946666576,
          143.46313753683208,
          144.2105694634214,
          144.92895343820254,
          145.61995054845391,
          146.2850975995903,
          146.9258185251374,
          147.5434345623493,
          148.13917334650083,
          148.71417705558156,
          149.26950971909295,
          149.80616378935193,
          150.3250660606847,
          150.82708301078074,
          151.313025628964,
          151.78365378797642,
          152.23968020884323,
          152.68177406233343,
          153.11056424529085,
          153.52664236557064,
          153.93056546537684,
          154.32285850936248,
          154.7040166608619,
          155.07450736700955,
          155.43477227120567,
          155.78522896938213,
          156.12627262475144,
          156.45827745416653,
          156.7815980978457,
          157.09657088300142,
          157.40351499084016,
          157.70273353544394,
          157.99451456220032,
          158.2791319726943,
          158.55684638230417,
          158.82790591614543,
          159.0925469484728,
          159.35099479017106,
          159.60346432853774,
          159.85016062317595,
          160.09127946147007,
          160.32700787680758,
          160.55752463242825,
          160.78300067353342,
          161.00359955005624,
          161.21947781229179,
          161.43078538139753,
          161.6376658966068,
          161.8402570408458,
          162.03869084630446,
          162.2330939813865,
          162.4235880203499,
          162.6102896968428,
          162.79331114244673,
          162.97276011125106,
          163.14874019140376,
          163.32135100451157,
          163.49068839369528,
          163.6568446010472,
          163.8199084351799,
          163.97996542950682,
          164.1370979918466
         ]
        },
        {
         "mode": "lines",
         "name": "Gurobi: Vmax=195.3, Km=3.3 (MSE=7.6)",
         "type": "scatter",
         "x": [
          0.05,
          0.3525252525252525,
          0.6550505050505051,
          0.9575757575757576,
          1.2601010101010102,
          1.5626262626262626,
          1.8651515151515152,
          2.1676767676767676,
          2.47020202020202,
          2.7727272727272725,
          3.075252525252525,
          3.3777777777777778,
          3.68030303030303,
          3.9828282828282826,
          4.2853535353535355,
          4.587878787878788,
          4.89040404040404,
          5.192929292929293,
          5.495454545454545,
          5.797979797979798,
          6.10050505050505,
          6.403030303030303,
          6.705555555555556,
          7.008080808080808,
          7.3106060606060606,
          7.613131313131313,
          7.915656565656565,
          8.218181818181819,
          8.520707070707072,
          8.823232323232324,
          9.125757575757577,
          9.428282828282828,
          9.730808080808082,
          10.033333333333335,
          10.335858585858587,
          10.63838383838384,
          10.940909090909091,
          11.243434343434345,
          11.545959595959596,
          11.84848484848485,
          12.151010101010101,
          12.453535353535354,
          12.756060606060608,
          13.05858585858586,
          13.361111111111112,
          13.663636363636364,
          13.966161616161617,
          14.268686868686869,
          14.571212121212122,
          14.873737373737375,
          15.176262626262627,
          15.47878787878788,
          15.781313131313132,
          16.083838383838383,
          16.386363636363637,
          16.68888888888889,
          16.991414141414143,
          17.293939393939397,
          17.596464646464646,
          17.8989898989899,
          18.201515151515153,
          18.504040404040406,
          18.806565656565656,
          19.10909090909091,
          19.411616161616163,
          19.714141414141416,
          20.01666666666667,
          20.31919191919192,
          20.621717171717172,
          20.924242424242426,
          21.22676767676768,
          21.52929292929293,
          21.831818181818182,
          22.134343434343435,
          22.43686868686869,
          22.739393939393942,
          23.04191919191919,
          23.344444444444445,
          23.6469696969697,
          23.94949494949495,
          24.2520202020202,
          24.554545454545455,
          24.857070707070708,
          25.15959595959596,
          25.462121212121215,
          25.764646464646464,
          26.067171717171718,
          26.36969696969697,
          26.672222222222224,
          26.974747474747478,
          27.277272727272727,
          27.57979797979798,
          27.882323232323234,
          28.184848484848487,
          28.487373737373737,
          28.78989898989899,
          29.092424242424244,
          29.394949494949497,
          29.69747474747475,
          30
         ],
         "y": [
          2.9462211863341357,
          19.03529177248449,
          32.641168117967894,
          44.297550401679445,
          54.39542263700262,
          63.227767826430984,
          71.01846255192618,
          77.9415177060723,
          84.13422985964233,
          89.70637898870892,
          94.74679305939901,
          99.32811887683263,
          103.51034610783071,
          107.34344876698525,
          110.8693916885595,
          114.12367322377494,
          117.13652458703861,
          119.9338518166985,
          122.5379825671758,
          124.96826333510546,
          127.2415409378701,
          129.37255359730085,
          131.37425082742845,
          133.25805680224545,
          135.03408852093932,
          136.71133757015434,
          138.29782237808803,
          139.80071640209982,
          141.22645657407386,
          142.58083546202852,
          143.8690799309518,
          145.0959185552243,
          146.2656396155412,
          147.38214117967294,
          148.44897449960717,
          149.46938174306646,
          150.4463289039657,
          151.38253459549114,
          152.28049531449818,
          153.14250767165458,
          153.97068800413652,
          154.76698972350735,
          155.533218698142,
          156.27104692517443,
          156.98202470982943,
          157.66759153885596,
          158.32908580855135,
          158.96775354571545,
          159.584756241102,
          160.18117789898062,
          160.75803139282576,
          161.31626420552604,
          161.85676362254472,
          162.38036143790538,
          162.88783822549976,
          163.37992722185004,
          163.85731786094516,
          164.32065899698804,
          164.77056184673566,
          165.20760267948748,
          165.63232527961827,
          166.04524320378093,
          166.4468418524795,
          166.83758037358302,
          167.2178934134731,
          167.5881927298672,
          167.94886867889997,
          168.30029158775199,
          168.64281302297348,
          168.9767669636345,
          169.30247088752967,
          169.62022677786527,
          169.93032205713604,
          170.2330304542643,
          170.52861281049894,
          170.81731782906144,
          171.099382773068,
          171.3750341158428,
          171.64448814737003,
          171.90795154029686,
          172.16562187860026,
          172.4176881517601,
          172.66433121703608,
          172.90572423222397,
          173.1420330610685,
          173.3734166533265,
          173.6000274013112,
          173.82201147459767,
          174.03950913443495,
          174.2526550292854,
          174.4615784727998,
          174.66640370543467,
          174.86725014082353,
          175.06423259792822,
          175.25746151992016,
          175.4470431806661,
          175.63307987963168,
          175.81567012595227,
          175.99490881236767,
          176.1708873796662
         ]
        }
       ],
       "layout": {
        "template": {
         "data": {
          "bar": [
           {
            "error_x": {
             "color": "rgb(36,36,36)"
            },
            "error_y": {
             "color": "rgb(36,36,36)"
            },
            "marker": {
             "line": {
              "color": "white",
              "width": 0.5
             },
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "bar"
           }
          ],
          "barpolar": [
           {
            "marker": {
             "line": {
              "color": "white",
              "width": 0.5
             },
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "barpolar"
           }
          ],
          "carpet": [
           {
            "aaxis": {
             "endlinecolor": "rgb(36,36,36)",
             "gridcolor": "white",
             "linecolor": "white",
             "minorgridcolor": "white",
             "startlinecolor": "rgb(36,36,36)"
            },
            "baxis": {
             "endlinecolor": "rgb(36,36,36)",
             "gridcolor": "white",
             "linecolor": "white",
             "minorgridcolor": "white",
             "startlinecolor": "rgb(36,36,36)"
            },
            "type": "carpet"
           }
          ],
          "choropleth": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "type": "choropleth"
           }
          ],
          "contour": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "colorscale": [
             [
              0,
              "#440154"
             ],
             [
              0.1111111111111111,
              "#482878"
             ],
             [
              0.2222222222222222,
              "#3e4989"
             ],
             [
              0.3333333333333333,
              "#31688e"
             ],
             [
              0.4444444444444444,
              "#26828e"
             ],
             [
              0.5555555555555556,
              "#1f9e89"
             ],
             [
              0.6666666666666666,
              "#35b779"
             ],
             [
              0.7777777777777778,
              "#6ece58"
             ],
             [
              0.8888888888888888,
              "#b5de2b"
             ],
             [
              1,
              "#fde725"
             ]
            ],
            "type": "contour"
           }
          ],
          "contourcarpet": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "type": "contourcarpet"
           }
          ],
          "heatmap": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "colorscale": [
             [
              0,
              "#440154"
             ],
             [
              0.1111111111111111,
              "#482878"
             ],
             [
              0.2222222222222222,
              "#3e4989"
             ],
             [
              0.3333333333333333,
              "#31688e"
             ],
             [
              0.4444444444444444,
              "#26828e"
             ],
             [
              0.5555555555555556,
              "#1f9e89"
             ],
             [
              0.6666666666666666,
              "#35b779"
             ],
             [
              0.7777777777777778,
              "#6ece58"
             ],
             [
              0.8888888888888888,
              "#b5de2b"
             ],
             [
              1,
              "#fde725"
             ]
            ],
            "type": "heatmap"
           }
          ],
          "heatmapgl": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "colorscale": [
             [
              0,
              "#440154"
             ],
             [
              0.1111111111111111,
              "#482878"
             ],
             [
              0.2222222222222222,
              "#3e4989"
             ],
             [
              0.3333333333333333,
              "#31688e"
             ],
             [
              0.4444444444444444,
              "#26828e"
             ],
             [
              0.5555555555555556,
              "#1f9e89"
             ],
             [
              0.6666666666666666,
              "#35b779"
             ],
             [
              0.7777777777777778,
              "#6ece58"
             ],
             [
              0.8888888888888888,
              "#b5de2b"
             ],
             [
              1,
              "#fde725"
             ]
            ],
            "type": "heatmapgl"
           }
          ],
          "histogram": [
           {
            "marker": {
             "line": {
              "color": "white",
              "width": 0.6
             }
            },
            "type": "histogram"
           }
          ],
          "histogram2d": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "colorscale": [
             [
              0,
              "#440154"
             ],
             [
              0.1111111111111111,
              "#482878"
             ],
             [
              0.2222222222222222,
              "#3e4989"
             ],
             [
              0.3333333333333333,
              "#31688e"
             ],
             [
              0.4444444444444444,
              "#26828e"
             ],
             [
              0.5555555555555556,
              "#1f9e89"
             ],
             [
              0.6666666666666666,
              "#35b779"
             ],
             [
              0.7777777777777778,
              "#6ece58"
             ],
             [
              0.8888888888888888,
              "#b5de2b"
             ],
             [
              1,
              "#fde725"
             ]
            ],
            "type": "histogram2d"
           }
          ],
          "histogram2dcontour": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "colorscale": [
             [
              0,
              "#440154"
             ],
             [
              0.1111111111111111,
              "#482878"
             ],
             [
              0.2222222222222222,
              "#3e4989"
             ],
             [
              0.3333333333333333,
              "#31688e"
             ],
             [
              0.4444444444444444,
              "#26828e"
             ],
             [
              0.5555555555555556,
              "#1f9e89"
             ],
             [
              0.6666666666666666,
              "#35b779"
             ],
             [
              0.7777777777777778,
              "#6ece58"
             ],
             [
              0.8888888888888888,
              "#b5de2b"
             ],
             [
              1,
              "#fde725"
             ]
            ],
            "type": "histogram2dcontour"
           }
          ],
          "mesh3d": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "type": "mesh3d"
           }
          ],
          "parcoords": [
           {
            "line": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "parcoords"
           }
          ],
          "pie": [
           {
            "automargin": true,
            "type": "pie"
           }
          ],
          "scatter": [
           {
            "fillpattern": {
             "fillmode": "overlay",
             "size": 10,
             "solidity": 0.2
            },
            "type": "scatter"
           }
          ],
          "scatter3d": [
           {
            "line": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scatter3d"
           }
          ],
          "scattercarpet": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scattercarpet"
           }
          ],
          "scattergeo": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scattergeo"
           }
          ],
          "scattergl": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scattergl"
           }
          ],
          "scattermapbox": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scattermapbox"
           }
          ],
          "scatterpolar": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scatterpolar"
           }
          ],
          "scatterpolargl": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scatterpolargl"
           }
          ],
          "scatterternary": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 1,
              "tickcolor": "rgb(36,36,36)",
              "ticks": "outside"
             }
            },
            "type": "scatterternary"
           }
          ],
          "surface": [
           {
            "colorbar": {
             "outlinewidth": 1,
             "tickcolor": "rgb(36,36,36)",
             "ticks": "outside"
            },
            "colorscale": [
             [
              0,
              "#440154"
             ],
             [
              0.1111111111111111,
              "#482878"
             ],
             [
              0.2222222222222222,
              "#3e4989"
             ],
             [
              0.3333333333333333,
              "#31688e"
             ],
             [
              0.4444444444444444,
              "#26828e"
             ],
             [
              0.5555555555555556,
              "#1f9e89"
             ],
             [
              0.6666666666666666,
              "#35b779"
             ],
             [
              0.7777777777777778,
              "#6ece58"
             ],
             [
              0.8888888888888888,
              "#b5de2b"
             ],
             [
              1,
              "#fde725"
             ]
            ],
            "type": "surface"
           }
          ],
          "table": [
           {
            "cells": {
             "fill": {
              "color": "rgb(237,237,237)"
             },
             "line": {
              "color": "white"
             }
            },
            "header": {
             "fill": {
              "color": "rgb(217,217,217)"
             },
             "line": {
              "color": "white"
             }
            },
            "type": "table"
           }
          ]
         },
         "layout": {
          "annotationdefaults": {
           "arrowhead": 0,
           "arrowwidth": 1
          },
          "autotypenumbers": "strict",
          "coloraxis": {
           "colorbar": {
            "outlinewidth": 1,
            "tickcolor": "rgb(36,36,36)",
            "ticks": "outside"
           }
          },
          "colorscale": {
           "diverging": [
            [
             0,
             "rgb(103,0,31)"
            ],
            [
             0.1,
             "rgb(178,24,43)"
            ],
            [
             0.2,
             "rgb(214,96,77)"
            ],
            [
             0.3,
             "rgb(244,165,130)"
            ],
            [
             0.4,
             "rgb(253,219,199)"
            ],
            [
             0.5,
             "rgb(247,247,247)"
            ],
            [
             0.6,
             "rgb(209,229,240)"
            ],
            [
             0.7,
             "rgb(146,197,222)"
            ],
            [
             0.8,
             "rgb(67,147,195)"
            ],
            [
             0.9,
             "rgb(33,102,172)"
            ],
            [
             1,
             "rgb(5,48,97)"
            ]
           ],
           "sequential": [
            [
             0,
             "#440154"
            ],
            [
             0.1111111111111111,
             "#482878"
            ],
            [
             0.2222222222222222,
             "#3e4989"
            ],
            [
             0.3333333333333333,
             "#31688e"
            ],
            [
             0.4444444444444444,
             "#26828e"
            ],
            [
             0.5555555555555556,
             "#1f9e89"
            ],
            [
             0.6666666666666666,
             "#35b779"
            ],
            [
             0.7777777777777778,
             "#6ece58"
            ],
            [
             0.8888888888888888,
             "#b5de2b"
            ],
            [
             1,
             "#fde725"
            ]
           ],
           "sequentialminus": [
            [
             0,
             "#440154"
            ],
            [
             0.1111111111111111,
             "#482878"
            ],
            [
             0.2222222222222222,
             "#3e4989"
            ],
            [
             0.3333333333333333,
             "#31688e"
            ],
            [
             0.4444444444444444,
             "#26828e"
            ],
            [
             0.5555555555555556,
             "#1f9e89"
            ],
            [
             0.6666666666666666,
             "#35b779"
            ],
            [
             0.7777777777777778,
             "#6ece58"
            ],
            [
             0.8888888888888888,
             "#b5de2b"
            ],
            [
             1,
             "#fde725"
            ]
           ]
          },
          "colorway": [
           "#1F77B4",
           "#FF7F0E",
           "#2CA02C",
           "#D62728",
           "#9467BD",
           "#8C564B",
           "#E377C2",
           "#7F7F7F",
           "#BCBD22",
           "#17BECF"
          ],
          "font": {
           "color": "rgb(36,36,36)"
          },
          "geo": {
           "bgcolor": "white",
           "lakecolor": "white",
           "landcolor": "white",
           "showlakes": true,
           "showland": true,
           "subunitcolor": "white"
          },
          "hoverlabel": {
           "align": "left"
          },
          "hovermode": "closest",
          "mapbox": {
           "style": "light"
          },
          "paper_bgcolor": "white",
          "plot_bgcolor": "white",
          "polar": {
           "angularaxis": {
            "gridcolor": "rgb(232,232,232)",
            "linecolor": "rgb(36,36,36)",
            "showgrid": false,
            "showline": true,
            "ticks": "outside"
           },
           "bgcolor": "white",
           "radialaxis": {
            "gridcolor": "rgb(232,232,232)",
            "linecolor": "rgb(36,36,36)",
            "showgrid": false,
            "showline": true,
            "ticks": "outside"
           }
          },
          "scene": {
           "xaxis": {
            "backgroundcolor": "white",
            "gridcolor": "rgb(232,232,232)",
            "gridwidth": 2,
            "linecolor": "rgb(36,36,36)",
            "showbackground": true,
            "showgrid": false,
            "showline": true,
            "ticks": "outside",
            "zeroline": false,
            "zerolinecolor": "rgb(36,36,36)"
           },
           "yaxis": {
            "backgroundcolor": "white",
            "gridcolor": "rgb(232,232,232)",
            "gridwidth": 2,
            "linecolor": "rgb(36,36,36)",
            "showbackground": true,
            "showgrid": false,
            "showline": true,
            "ticks": "outside",
            "zeroline": false,
            "zerolinecolor": "rgb(36,36,36)"
           },
           "zaxis": {
            "backgroundcolor": "white",
            "gridcolor": "rgb(232,232,232)",
            "gridwidth": 2,
            "linecolor": "rgb(36,36,36)",
            "showbackground": true,
            "showgrid": false,
            "showline": true,
            "ticks": "outside",
            "zeroline": false,
            "zerolinecolor": "rgb(36,36,36)"
           }
          },
          "shapedefaults": {
           "fillcolor": "black",
           "line": {
            "width": 0
           },
           "opacity": 0.3
          },
          "ternary": {
           "aaxis": {
            "gridcolor": "rgb(232,232,232)",
            "linecolor": "rgb(36,36,36)",
            "showgrid": false,
            "showline": true,
            "ticks": "outside"
           },
           "baxis": {
            "gridcolor": "rgb(232,232,232)",
            "linecolor": "rgb(36,36,36)",
            "showgrid": false,
            "showline": true,
            "ticks": "outside"
           },
           "bgcolor": "white",
           "caxis": {
            "gridcolor": "rgb(232,232,232)",
            "linecolor": "rgb(36,36,36)",
            "showgrid": false,
            "showline": true,
            "ticks": "outside"
           }
          },
          "title": {
           "x": 0.05
          },
          "xaxis": {
           "automargin": true,
           "gridcolor": "rgb(232,232,232)",
           "linecolor": "rgb(36,36,36)",
           "showgrid": false,
           "showline": true,
           "ticks": "outside",
           "title": {
            "standoff": 15
           },
           "zeroline": false,
           "zerolinecolor": "rgb(36,36,36)"
          },
          "yaxis": {
           "automargin": true,
           "gridcolor": "rgb(232,232,232)",
           "linecolor": "rgb(36,36,36)",
           "showgrid": false,
           "showline": true,
           "ticks": "outside",
           "title": {
            "standoff": 15
           },
           "zeroline": false,
           "zerolinecolor": "rgb(36,36,36)"
          }
         }
        },
        "title": {
         "text": "Nonlinear regression example: Reaction Speed vs Concentration"
        },
        "xaxis": {
         "title": {
          "text": "x"
         }
        },
        "yaxis": {
         "title": {
          "text": "y"
         }
        }
       }
      }
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Create a figure\n",
    "def ylist_MM(xlist, Vmax, Km):\n",
    "    return [Vmax*x/(Km + x) for x in xlist]\n",
    "def mean_squared_error(xlist, ylist, Vmax, Km):\n",
    "    y = ylist_MM(xlist, Vmax, Km)\n",
    "    return np.sum((y-ylist)**2)/len(xlist)\n",
    "\n",
    "# Create a figure\n",
    "fig = go.Figure()\n",
    "xtrain_arr = np.array(list(xtrain.values()))\n",
    "ytrain_arr = np.array(list(ytrain.values()))\n",
    "xplot = np.linspace(min(xtrain_arr), max(xtrain_arr), 100)\n",
    "\n",
    "# Add the first line\n",
    "fig.add_trace(go.Scatter(x=xtrain_arr, y=ytrain_arr, mode='markers', name='Training data'))\n",
    "\n",
    "# Add the second line\n",
    "label = \"dir-MM (R-method)\"\n",
    "this_Vmax = 181.18\n",
    "this_Km = 3.115\n",
    "MSE = mean_squared_error(xtrain_arr, ytrain_arr, this_Vmax, this_Km)\n",
    "fig.add_trace(go.Scatter(x=xplot, y=ylist_MM(xplot,this_Vmax, this_Km), mode='lines', name=f\"{label}: Vmax={round(this_Vmax,1)}, Km={round(this_Km,1)} (MSE={round(MSE,1)})\"))\n",
    "\n",
    "# Add the second line\n",
    "label = \"Gurobi\"\n",
    "MSE = mean_squared_error(xtrain_arr, ytrain_arr, Vmax.X, Km.X)\n",
    "fig.add_trace(go.Scatter(x=xplot, y=ylist_MM(xplot,Vmax.X, Km.X), mode='lines', name=f\"{label}: Vmax={round(Vmax.X,1)}, Km={round(Km.X,1)} (MSE={round(MSE,1)})\"))\n",
    "\n",
    "\n",
    "# Update layout (optional)\n",
    "fig.update_layout(title='Nonlinear regression example: Reaction Speed vs Concentration',\n",
    "                  xaxis_title='x',\n",
    "                  yaxis_title='y',)\n",
    "fig.update_layout(template='simple_white')\n",
    "# Show the figure\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec8c0a51-6cc2-485e-81ea-dde62d857eb6",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-warning alert-dismissible\">\n",
    "  <a href=\"#\" class=\"close\" data-dismiss=\"alert\" aria-label=\"close\">&times;</a>\n",
    "    <strong>Refresher: Whole model at a glance</strong> \n",
    "    <p>We created this model over several different cells so we could dig into how and why each step is done.  When we put all the code together, the model is written in only a few lines of code.  Here is the complete code to write and solve this model after you have the data available:</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c02b4f8",
   "metadata": {},
   "source": [
    "```python\n",
    "# Write and solve the nonlinear regression example:\n",
    "import gurobipy as gp\n",
    "from gurobipy import GRB\n",
    "#\n",
    "# Declare and initialize model\n",
    "model = gp.Model(\"Nonlinear regression\")\n",
    "#\n",
    "# Add variables\n",
    "# Create decision variables \n",
    "Vmax = model.addVar(name='Vmax', ub=1000)\n",
    "Km = model.addVar(name='Km', ub=1000)\n",
    "ymodel = model.addVars(points, lb=-2*ymax, ub=2*ymax, name=\"ymodel\")\n",
    "#\n",
    "# Add constraints\n",
    "constrs = model.addConstrs((ymodel[i]*(Km + xtrain[i]) == Vmax*xtrain[i] for i in points), name=\"Define_ymodel\")\n",
    "#\n",
    "# Add the objective\n",
    "model.setObjective(sum((ymodel[i] - ytrain[i])**2 for i in points), sense=GRB.MINIMIZE)\n",
    "#\n",
    "# Solve the model\n",
    "model.optimize()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e8085a38-9f4c-4f7b-a1a9-ac65b781e422",
   "metadata": {},
   "source": [
    "## Before you exit, free up Gurobi resources\n",
    "After you are done, it is a best practice to free up any Gurobi resources associated with the model object and environment.  This will release any shared licenses and end the job on the cloud or compute server.  \n",
    "\n",
    "To do this, call [Model.dispose()](https://www.gurobi.com/documentation/current/refman/py_model_dispose.html#pythonmethod:Model.dispose) on all Model objects, [Env.dispose()](https://www.gurobi.com/documentation/current/refman/py_env_dispose.html#pythonmethod:Env.dispose) on any Env objects you created, or [disposeDefaultEnv()](https://www.gurobi.com/documentation/current/refman/py_disposedefaultenv.html#pythonmethod:disposeDefaultEnv) if you used the default environment instead."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "f98c1ae2-f9e8-4a9e-b600-45906b50224d",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Freeing default Gurobi environment\n"
     ]
    }
   ],
   "source": [
    "# Free Gurobi resources: Model and environment\n",
    "model.dispose()\n",
    "gp.disposeDefaultEnv()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "gurobi_ml",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
